#![allow(dead_code)]

use anyhow::Result;
use std::future::Future;
use std::pin::Pin;

macro_rules! goroutine_template {
    ($out:ty,$cap_name:tt,$new:tt,$on:tt,$go:tt) => {
        pub type $cap_name<T> = Pin<Box<dyn Future<Output = $out> + Send>>;
        pub fn $new<F, T>(function: F) -> $cap_name<T>
        where
            F: Future<Output = $out> + Send + 'static,
        {
            Box::pin(function)
        }
        pub async fn $on<F, T>(function: F) -> $out
        where
            F: Future<Output = $out> + Send + 'static,
        {
            $new(function).await
        }
        pub fn $go<F, T>(function: F)
        where
            F: Future<Output = $out> + Send + 'static,
            T: std::marker::Send + 'static,
        {
            tokio::spawn($new(function));
        }
    };
}

goroutine_template!(T, Goroutine, new, on, go);
goroutine_template!(
    Result<T>,
    GoroutineResult,
    new_res_future,
    on_result,
    go_result
);

#[cfg(test)]
mod test {
    use crate::infra::{go, new, on};

    #[tokio::test]
    async fn test_new() {
        let res = new(async { "hello world" }).await;
        assert_eq!(res, "hello world");
    }
    #[tokio::test]
    async fn test_on() {
        let res = on(async { "hello world" }).await;
        assert_eq!(res, "hello world");
    }
    #[tokio::test]
    async fn test_go() {
        let (sender, receiver) = async_channel::unbounded();
        go(async move {
            let _ = sender.send("hello world").await;
        });
        let msg = receiver.recv().await.unwrap_or_else(|err| {
            Box::leak(Box::new(format!("receive error:{}", err.to_string())))
        });
        assert_eq!(msg, "hello world");
    }
}
